﻿<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>文字动画+文本编辑器</title>

<style>
@import "https://fonts.googleapis.com/css?family=Baloo+Chettan|Gloria+Hallelujah";
html, body, .anim-wrap {
  width: 100%;
  height: 100%;
  margin: 0;
}

body {
  font-family: Helvetica Neueu, HelveticaNeueu, Helvetica, Arial, sans-serif;
  overflow: hidden;
}

.anim-wrap {
  position: absolute;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
}

.anim-text {
  font: 4em "Baloo Chettan", Helvetica Neueu, HelveticaNeueu, Helvetica, Arial;
  white-space: nowrap;
  min-width: 1em;
  min-height: 1.2em;
  border-bottom: 1px solid transparent;
  caret-color: #f20dcc;
}
.anim-text:empty, .anim-text:focus {
  border-bottom-color: #f20dcc;
  outline: none;
}

.char {
  position: relative;
  margin: 0 .05em;
}

.char,
.letter-inner {
  display: inline-block;
}

.anim[data-effect=fade] > .letter {
  animation: fadeIn 1.5s backwards;
}
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: scale(2);
  }
}
.anim[data-effect=slide] > .letter {
  animation: slide 1s backwards;
  transform-origin: bottom left;
}
@keyframes slide {
  0% {
    transform: translateX(90vw) skew(-17deg) scaleX(3);
    animation-timing-function: ease-in;
  }
  80% {
    transform: translateX(0) skew(-17deg) scaleX(3);
    animation-timing-function: ease-out;
  }
  88% {
    transform: translateX(0) skew(12deg) scaleX(0.8);
    animation-timing-function: ease-in-out;
  }
  95% {
    transform: translateX(0) skew(-5deg) scaleX(1);
    animation-timing-function: ease-in-out;
  }
  100% {
    transform: translateX(0) skew(0deg) scaleX(1);
    animation-timing-function: ease-in-out;
  }
}
@keyframes slideIn {
  from {
    transform: translateX(70vw);
  }
}
.anim[data-effect=roll] > .letter {
  animation: rollIn 1.1s cubic-bezier(0, 0, 0.6, 1) backwards;
}
@keyframes rollIn {
  from {
    transform: translateX(90vw) rotate(1200deg);
  }
}
.anim[data-effect=peel] > .letter {
  animation: peelIn 2s backwards ease-in-out;
}
.anim[data-effect=peel] > .letter > .letter-inner {
  animation: rotateY 1.3s 0.7s backwards ease-in;
}
@keyframes peelIn {
  from {
    transform: translate(-100vw, 0);
  }
}
@keyframes rotateY {
  from {
    transform: scale(3) rotateY(180deg);
  }
}
.anim[data-effect=swivel] > .letter:not(.space)::before {
  background: currentColor;
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  opacity: .2;
}
.anim[data-effect=swivel] {
  perspective: 100vmax;
  perspective-origin: top;
}
.anim[data-effect=swivel] > .letter {
  padding: 0 .1em;
  animation: swivel 5s backwards;
  transform-origin: top;
}
@keyframes swivel {
  0% {
    transform: rotateX(-90deg);
  }
  10% {
    transform: rotateX(82deg);
  }
  20% {
    transform: rotateX(-74deg);
  }
  30% {
    transform: rotateX(66deg);
  }
  39% {
    transform: rotateX(-58deg);
  }
  48% {
    transform: rotateX(50deg);
  }
  56% {
    transform: rotateX(-42deg);
  }
  63% {
    transform: rotateX(35deg);
  }
  70% {
    transform: rotateX(-28deg);
  }
  77% {
    transform: rotateX(21deg);
  }
  83% {
    transform: rotateX(-15deg);
  }
  89% {
    transform: rotateX(9deg);
  }
  95% {
    transform: rotateX(-4deg);
  }
  100% {
    transform: none;
  }
}
.anim[data-effect=hop] > .letter {
  animation: slideIn 2s linear backwards;
}
.anim[data-effect=hop] > .letter > .letter-inner {
  animation: hop .1s 20 alternate;
}
@keyframes hop {
  to {
    transform: translateY(-0.6em);
  }
}
.anim[data-effect=wave] {
  animation: slideIn 3s linear backwards;
}
.anim[data-effect=wave] > .letter {
  animation: hop .15s 20 alternate;
}
.anim[data-effect=wave2] {
  animation: slideIn 3s linear backwards;
}
.anim[data-effect=wave2] > .letter {
  animation: inflate .5s 6 linear alternate;
}
@keyframes inflate {
  to {
    transform: scale(2);
  }
}
.anim[data-effect=converge] > .letter {
  animation: converge 2.5s forwards;
}
.anim[data-effect=converge] > .letter:nth-child(1n) {
  transform: translate(75vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(2n) {
  transform: translate(100vw, 90vh);
}
.anim[data-effect=converge] > .letter:nth-child(3n) {
  transform: translate(-100vw, 96vh);
}
.anim[data-effect=converge] > .letter:nth-child(4n) {
  transform: translate(1vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(5n) {
  transform: translate(100vw, 24vh);
}
.anim[data-effect=converge] > .letter:nth-child(6n) {
  transform: translate(81vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(7n) {
  transform: translate(-22vw, -100vh);
}
.anim[data-effect=converge] > .letter:nth-child(8n) {
  transform: translate(46vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(9n) {
  transform: translate(-5vw, -100vh);
}
.anim[data-effect=converge] > .letter:nth-child(10n) {
  transform: translate(-45vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(11n) {
  transform: translate(100vw, 49vh);
}
.anim[data-effect=converge] > .letter:nth-child(12n) {
  transform: translate(100vw, 75vh);
}
.anim[data-effect=converge] > .letter:nth-child(13n) {
  transform: translate(100vw, -6vh);
}
.anim[data-effect=converge] > .letter:nth-child(14n) {
  transform: translate(93vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(15n) {
  transform: translate(22vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(16n) {
  transform: translate(100vw, -47vh);
}
.anim[data-effect=converge] > .letter:nth-child(17n) {
  transform: translate(100vw, 47vh);
}
.anim[data-effect=converge] > .letter:nth-child(18n) {
  transform: translate(100vw, -13vh);
}
.anim[data-effect=converge] > .letter:nth-child(19n) {
  transform: translate(45vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(20n) {
  transform: translate(100vw, -73vh);
}
.anim[data-effect=converge] > .letter:nth-child(21n) {
  transform: translate(100vw, 29vh);
}
.anim[data-effect=converge] > .letter:nth-child(22n) {
  transform: translate(-48vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(23n) {
  transform: translate(86vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(24n) {
  transform: translate(-100vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(25n) {
  transform: translate(-8vw, -100vh);
}
.anim[data-effect=converge] > .letter:nth-child(26n) {
  transform: translate(-100vw, 44vh);
}
.anim[data-effect=converge] > .letter:nth-child(27n) {
  transform: translate(100vw, 35vh);
}
.anim[data-effect=converge] > .letter:nth-child(28n) {
  transform: translate(41vw, -100vh);
}
.anim[data-effect=converge] > .letter:nth-child(29n) {
  transform: translate(20vw, 100vh);
}
.anim[data-effect=converge] > .letter:nth-child(30n) {
  transform: translate(23vw, -100vh);
}
@keyframes converge {
  to {
    transform: translate(0, 0);
  }
}
.anim[data-effect=spiral] > .letter {
  animation: spiral 3s ease-in both;
}
@keyframes spiral {
  from {
    transform: rotate(720deg) translateY(-60vmax);
  }
}
.anim[data-effect=snow] > .letter {
  animation: snow 5s cubic-bezier(0.68, 0.21, 0.7, 1) both;
}
.anim[data-effect=snow] > .letter > .letter-inner {
  animation: sway 1s cubic-bezier(0.46, 0.03, 0.52, 0.96) 5 alternate;
}
@keyframes snow {
  from {
    transform: translateY(-60vh);
  }
}
@keyframes sway {
  from {
    transform: translate(2em, 0);
  }
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .char {
  margin: 0 .15em;
  animation: 0.5s cubic-bezier(0.33, 0.08, 0.7, 0.32) forwards;
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 1) {
  animation-name: meteorite;
  transform: translate(-6.48389vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 1) > .letter-inner {
  transform: translate(0.00419em, 0.00817em) rotate(4.47376deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 2) {
  animation-name: meteorite;
  transform: translate(5.18049vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 2) > .letter-inner {
  transform: translate(-0.07254em, 0.13755em) rotate(5.32154deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 3) {
  animation-name: meteorite;
  transform: translate(2.06164vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 3) > .letter-inner {
  transform: translate(0.07569em, -0.06822em) rotate(-2.04423deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 4) {
  animation-name: meteorite;
  transform: translate(11.67581vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 4) > .letter-inner {
  transform: translate(-0.08246em, -0.05575em) rotate(6.00681deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 5) {
  animation-name: meteorite;
  transform: translate(-5.87888vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 5) > .letter-inner {
  transform: translate(-0.09508em, 0.03611em) rotate(9.84507deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 6) {
  animation-name: meteorite;
  transform: translate(-12.65403vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 6) > .letter-inner {
  transform: translate(0.09428em, -0.11027em) rotate(-3.95578deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 7) {
  animation-name: meteorite;
  transform: translate(-11.97834vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 7) > .letter-inner {
  transform: translate(0.09765em, 0.12939em) rotate(0.71265deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 8) {
  animation-name: meteorite;
  transform: translate(20.20496vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 8) > .letter-inner {
  transform: translate(0.01126em, 0.06191em) rotate(-8.7214deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 9) {
  animation-name: meteorite;
  transform: translate(-12.55705vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 9) > .letter-inner {
  transform: translate(-0.03434em, 0.02092em) rotate(-7.69682deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 10) {
  animation-name: meteorite;
  transform: translate(5.38038vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 10) > .letter-inner {
  transform: translate(-0.08378em, 0.05044em) rotate(1.84531deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 11) {
  animation-name: meteorite;
  transform: translate(15.69186vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 11) > .letter-inner {
  transform: translate(0.02974em, 0.02838em) rotate(4.91783deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 12) {
  animation-name: meteorite;
  transform: translate(19.18768vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 12) > .letter-inner {
  transform: translate(-0.06485em, 0.04586em) rotate(-8.62222deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 13) {
  animation-name: meteorite;
  transform: translate(-17.87654vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 13) > .letter-inner {
  transform: translate(-0.09121em, 0.14768em) rotate(-7.50893deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 14) {
  animation-name: meteorite;
  transform: translate(11.51431vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 14) > .letter-inner {
  transform: translate(0.07816em, 0.05717em) rotate(3.5439deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 15) {
  animation-name: meteorite;
  transform: translate(-12.47477vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 15) > .letter-inner {
  transform: translate(-0.05039em, -0.00396em) rotate(9.19099deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 16) {
  animation-name: meteorite;
  transform: translate(-11.11034vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 16) > .letter-inner {
  transform: translate(0.00633em, 0.06642em) rotate(5.04956deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 17) {
  animation-name: meteorite;
  transform: translate(9.87686vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 17) > .letter-inner {
  transform: translate(-0.0775em, 0.13518em) rotate(6.98645deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 18) {
  animation-name: meteorite;
  transform: translate(-21.54326vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 18) > .letter-inner {
  transform: translate(0.08587em, 0.09876em) rotate(1.97567deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 19) {
  animation-name: meteorite;
  transform: translate(-2.42744vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 19) > .letter-inner {
  transform: translate(-0.03162em, 0.0945em) rotate(-6.59714deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 20) {
  animation-name: meteorite;
  transform: translate(2.77545vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 20) > .letter-inner {
  transform: translate(0.05479em, 0.06678em) rotate(-2.68341deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 21) {
  animation-name: meteorite;
  transform: translate(-3.54171vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 21) > .letter-inner {
  transform: translate(0.0764em, -0.04548em) rotate(3.42587deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 22) {
  animation-name: meteorite;
  transform: translate(-18.77978vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 22) > .letter-inner {
  transform: translate(0.02712em, -0.09712em) rotate(1.63188deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 23) {
  animation-name: meteorite;
  transform: translate(-20.94836vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 23) > .letter-inner {
  transform: translate(-0.07261em, 0.12161em) rotate(-3.91319deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 24) {
  animation-name: meteorite;
  transform: translate(-21.87819vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 24) > .letter-inner {
  transform: translate(-0.01437em, 0.0646em) rotate(-6.31852deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 25) {
  animation-name: meteorite;
  transform: translate(-20.51654vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 25) > .letter-inner {
  transform: translate(-0.0084em, -0.03249em) rotate(-3.0008deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 26) {
  animation-name: meteorite;
  transform: translate(-9.12595vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 26) > .letter-inner {
  transform: translate(-0.00576em, 0.13715em) rotate(2.74989deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 27) {
  animation-name: meteorite;
  transform: translate(-4.6315vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 27) > .letter-inner {
  transform: translate(-0.03443em, 0.06653em) rotate(-2.0605deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 28) {
  animation-name: meteorite;
  transform: translate(-6.03076vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 28) > .letter-inner {
  transform: translate(-0.02798em, -0.09215em) rotate(-6.00461deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 29) {
  animation-name: meteorite;
  transform: translate(20.93875vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 29) > .letter-inner {
  transform: translate(-0.07571em, 0.14403em) rotate(3.03374deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 30) {
  animation-name: meteorite;
  transform: translate(-10.57748vw, -60vh);
}
.anim[data-effect=meteorite] > .letter:nth-child(30n + 30) > .letter-inner {
  transform: translate(0.04664em, -0.04971em) rotate(5.38089deg);
}
@keyframes meteorite {
  to {
    transform: none;
  }
}
.anim[data-effect=bounce] > .letter {
  animation: bounce 2s ease-in both;
}
@keyframes bounce {
  0% {
    transform: translateY(-60vh);
    animation-timing-function: cubic-bezier(0.5, 0, 0.82, 0.52);
  }
  37% {
    transform: none;
    animation-timing-function: cubic-bezier(0.18, 0.48, 0.5, 1);
  }
  55% {
    transform: translateY(-25vh);
    animation-timing-function: cubic-bezier(0.5, 0, 0.82, 0.52);
  }
  73% {
    transform: none;
    animation-timing-function: cubic-bezier(0.18, 0.48, 0.5, 1);
  }
  81% {
    transform: translateY(-10vh);
    animation-timing-function: cubic-bezier(0.5, 0, 0.82, 0.52);
  }
  89% {
    transform: none;
    animation-timing-function: cubic-bezier(0.18, 0.48, 0.5, 1);
  }
  93% {
    transform: translateY(-4vh);
    animation-timing-function: cubic-bezier(0.5, 0, 0.82, 0.52);
  }
  97% {
    transform: none;
    animation-timing-function: cubic-bezier(0.18, 0.48, 0.5, 1);
  }
  98.5% {
    transform: translateY(-1.1vh);
    animation-timing-function: cubic-bezier(0.5, 0, 0.82, 0.52);
  }
  100% {
    transform: none;
    animation-timing-function: cubic-bezier(0.18, 0.48, 0.5, 1);
  }
}
.anim[data-effect=float] > .letter {
  animation: float 18s ease-in-out backwards;
}
.anim[data-effect=float] > .letter > .letter-inner {
  animation: float-perpetual ease-in-out 10s 18s infinite alternate;
}
@keyframes float {
  0% {
    transform: translateY(60vh);
  }
  33% {
    transform: translateY(-8vh);
  }
  51% {
    transform: translateY(6vh);
  }
  66% {
    transform: translateY(-4vh);
  }
  80% {
    transform: translateY(3vh);
  }
  91% {
    transform: translateY(-1.3vh);
  }
  100% {
    transform: none;
  }
}
@keyframes float-perpetual {
  0% {
    transform: translateY(0);
  }
  37% {
    transform: translateY(-1.5vh);
  }
  58% {
    transform: translateY(1.3vh);
  }
  81% {
    transform: translateY(-1vh);
  }
  100% {
    transform: translateY(0.8vh);
  }
}
.anim[data-effect=bubble] > .letter {
  position: relative;
  animation: bubble-letter 1.3s cubic-bezier(0, 0, 0.33, 1) both;
}
.anim[data-effect=bubble] > .letter::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  border: 3px solid;
  border-radius: 999px;
  animation: bubble 2.6s cubic-bezier(0, 0.41, 0.28, 1) both;
  animation-delay: inherit;
}
.anim[data-effect=bubble] > .letter::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: currentColor;
  border-radius: 999px;
  animation: bubble 2.6s cubic-bezier(0, 0.41, 0.28, 1) both;
  animation-delay: inherit;
  opacity: 0.3;
}
@keyframes bubble-letter {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes bubble {
  from {
    width: 0;
    height: 0;
  }
  to {
    width: 10em;
    height: 10em;
    opacity: 0;
    visibility: hidden;
  }
}
.btn, .control {
  padding: .5em 1em;
  font-size: 1.2em;
}

.btn {
  background: #f20dcc;
  border: 1px solid transparent;
  color: white;
  cursor: pointer;
  transition: .25s;
}

.btn:hover, .btn:focus {
  background: #f655db;
}

select {
  border: none;
  outline-color: #f20dcc;
}

#controls {
  position: absolute;
  right: 1px;
  top: 1px;
  border-bottom-left-radius: 5px;
  box-shadow: -1px 2px 13px rgba(51, 0, 43, 0.4);
}

#controls button {
  width: -moz-available;
  width: -webkit-fill-available;
  width: fill-available;
  border-bottom-left-radius: 5px;
}

.hide {
  display: none !important;
}

.alert {
  position: absolute;
  bottom: -1.6em;
  left: 50%;
  transform: translateX(-50%);
  padding: .4em 1em 2em;
  background: #f20dcc;
  color: #fff0fc;
  animation: 2s 40s peep-in both ease-in-out;
  border-radius: 5px;
  box-shadow: 0 -1px 6px 1px rgba(242, 13, 204, 0.3);
}
.alert.close {
  animation: peep-out .8s linear both;
}

@keyframes peep-in {
  0% {
    transform: translate(-50%, 100%);
    animation-timing-function: linear;
  }
  76% {
    transform: translate(-50%, -18%);
  }
  90% {
    transform: translate(-50%, -20%);
  }
  98% {
    transform: translate(-50%, 8%);
  }
  100% {
    transform: translate(-50%, 0);
  }
}
@keyframes peep-out {
  to {
    transform: translate(-50%, 100%);
    visibility: hidden;
  }
}
.dismiss {
  border: none;
  background: #ff70e7;
  color: white;
  padding: .2em .5em;
  cursor: pointer;
  margin-left: .2em;
}

.tip {
  position: absolute;
  color: #f20dcc;
  animation: show-tip .5s 15s ease-in both;
  font-size: 1.3rem;
  font-family: "Gloria Hallelujah", cursive;
}
@keyframes show-tip {
  from {
    visibility: hidden;
    opacity: 0;
  }
}
.tip .text {
  text-shadow: -1px 2px 4px rgba(51, 0, 43, 0.2);
}
.tip strong {
  font-weight: normal;
}
.tip svg {
  width: 110px;
  height: auto;
  -webkit-filter: drop-shadow(-1px 3px 5px rgba(51, 0, 43, 0.3));
  filter: drop-shadow(-1px 3px 5px rgba(51, 0, 43, 0.3));
  animation: show-tip .5s 15.1s ease-in both;
}
.tip svg path {
  fill: #f20dcc;
}
@keyframes show-tip-arrow {
  0% {
    visibility: hidden;
    opacity: 0;
    transform: scale(0);
  }
  85% {
    visibility: visible;
    opacity: 1;
    transform: scale(1.2);
  }
  100% {
    visibility: visible;
    opacity: 1;
    transform: none;
  }
}
.tip-effect {
  left: -120px;
  top: .5em;
  animation-delay: 20s;
}
.tip-effect .text {
  width: 110px;
  display: flex;
  justify-content: center;
  white-space: nowrap;
  margin-left: -40px;
}
.tip-effect svg {
  animation-delay: 20.1s;
}

.tip-type {
  display: flex;
  left: 50%;
  top: calc(50% - 6rem);
  transform: translateX(-50%);
  animation-delay: 12s;
}
.tip-type svg {
  animation-delay: 12.1s;
  width: 40px;
  height: 40px;
  margin-top: .8em;
  margin-left: 20px;
  transform: rotate(-9deg);
  -webkit-filter: drop-shadow(0px 1px 3px rgba(51, 0, 43, 0.3));
  filter: drop-shadow(0px 1px 3px rgba(51, 0, 43, 0.3));
}
</style>
</head>
<body>

<div class="anim-wrap">
	<div class="anim-text" contenteditable spellcheck="false">www.sucaihuo.com</div>
</div>

<div id="controls">
	<select name="selectEffect" id="selectEffect" class="control">
		<option value="fade">Fade in</option>
		<option value="slide">Slide in</option>
		<option value="roll">Roll in</option>
		<option value="hop">Hop in</option>
		<option value="converge">Converge</option>
		<option value="spiral">Spiral</option>
		<option value="snow">Snow</option>
		<option value="meteorite">Meteorite</option>
		<option value="bounce">Bounce</option>
		<option value="peel">Peel</option>
		<option value="swivel">Swivel</option>
		<option value="float">Float</option>
		<option value="bubble">Bubble</option>
	</select>
	<div>
		<button class="btn animate">Animate</button>
	</div>

	<div class="tip tip-effect">
		<svg width="100px" height="100px" viewBox="0 0 100 100">
			<path d="M87.7,10.2c0,0-0.5,0-1.6,0c-1,0-2.5,0-4.4,0.1c-0.9,0-2,0.1-3.1,0.2c-1.1,0-2.3,0.2-3.6,0.3c-1.3,0.1-2.7,0.4-4.1,0.6
				c-0.7,0.1-1.5,0.2-2.2,0.4c-0.7,0.2-1.5,0.3-2.3,0.5c-3.1,0.6-6.5,1.7-9.9,2.9c-3.4,1.3-7,2.8-10.5,4.8c-3.5,2-6.9,4.4-10.1,7.1
				c-3.2,2.8-6,6-8.6,9.4l-1.9,2.6l-1.8,2.6c-0.6,0.9-1.1,1.8-1.7,2.7c-0.6,0.9-1.1,1.8-1.7,2.7c-2.2,3.6-4.1,7.3-5.9,10.8
				C12.5,61.4,11,65,9.7,68.4c-0.6,1.7-1.3,3.4-1.8,5.1c-0.5,1.7-1,3.3-1.5,4.8c-0.9,3.1-1.6,6-2.2,8.6c-0.3,1.3-0.5,2.5-0.8,3.6
				c-0.2,1.1-0.4,2.1-0.6,3.1c-0.4,1.9-0.5,3.3-0.7,4.3c-0.2,1-0.2,1.5-0.2,1.5l0,0c0,0.2-0.2,0.3-0.4,0.3c-0.2,0-0.3-0.2-0.3-0.4
				c0,0,0.1-0.5,0.2-1.5c0.1-1,0.3-2.5,0.6-4.4c0.1-0.9,0.3-2,0.5-3.1C2.8,89.2,3,88,3.2,86.7c0.5-2.6,1.1-5.5,1.8-8.7
				c0.8-3.2,1.7-6.6,2.8-10.2c1.2-3.6,2.4-7.3,4-11.1c1.6-3.8,3.5-7.6,5.7-11.3c2.3-3.7,4.9-7.3,7.8-10.5c2.9-3.3,6.1-6.3,9.4-9.1
				c3.3-2.8,6.8-5.3,10.2-7.4c3.5-2.1,7.1-3.9,10.7-5.2c0.9-0.3,1.8-0.6,2.7-0.9c0.9-0.3,1.7-0.6,2.6-0.8c1.7-0.4,3.4-0.9,5-1.1
				c0.8-0.1,1.6-0.3,2.4-0.4c0.8-0.1,1.5-0.2,2.3-0.3c1.5-0.2,2.9-0.3,4.2-0.4c1.3,0,2.6-0.2,3.7-0.1c1.2,0,2.2,0,3.2,0
				c1.9,0,3.4,0.2,4.4,0.3c1,0.1,1.6,0.1,1.6,0.1c0.2,0,0.3,0.2,0.3,0.4C88,10.1,87.9,10.2,87.7,10.2z"/>
			<path d="M79.9,20.8c0.5-0.7,1.1-1.4,1.7-2c0.6-0.6,1.2-1.3,1.9-1.9c0.7-0.6,1.3-1.1,2-1.7c0.4-0.3,0.7-0.5,1.1-0.8
				c0.4-0.2,0.7-0.5,1.1-0.7c1.5-0.9,3-1.7,4.6-2.5l2.3-1.1c0.8-0.3,1.5-0.8,2.3-1.2l0,1.7L94,9.4c-0.9-0.5-1.9-1-2.9-1.4
				c-2-0.8-4-1.5-6-2.1L79,4c-1-0.3-2-0.7-3.1-1.1c-1-0.4-2-0.9-2.9-1.5c-0.1-0.1-0.1-0.3-0.1-0.4C73,0.9,73.2,0.9,73.3,1l0,0
				c0.8,0.6,1.8,1,2.8,1.3c1,0.4,2,0.6,3.1,0.9l6.2,1.6c2.1,0.5,4.2,1.1,6.2,1.8c1,0.3,2.1,0.7,3.1,1.1c1,0.4,2,0.9,3,1.4l1.6,1
				l-1.7,0.7c-1.6,0.7-3.1,1.4-4.7,2.2c-1.5,0.7-3,1.5-4.5,2.3c-0.7,0.4-1.4,0.9-2.1,1.3c-0.7,0.4-1.4,0.9-2.1,1.4
				c-0.7,0.5-1.4,1-2,1.5c-0.3,0.3-0.7,0.5-1,0.8l-1,0.9c-0.1,0.1-0.4,0.1-0.5,0C79.8,21.1,79.8,20.9,79.9,20.8z"/>
		</svg>
		<div class="text">
			<strong>Check out other effects</strong>
		</div>
	</div>
</div>

<div class="tip tip-type">
	<div class="text">
		<strong>Seriously, type something!</strong>
	</div>
	<svg width="100px" height="100px" viewBox="0 0 100 100">
		<path d="M90.3,85.8c0,0,0-0.5-0.1-1.5c-0.1-1-0.1-2.5-0.2-4.3c-0.1-0.9-0.2-1.9-0.2-3c-0.1-1.1-0.3-2.3-0.4-3.6
			c-0.1-0.6-0.1-1.3-0.3-2c-0.1-0.7-0.2-1.4-0.4-2.1c-0.1-0.7-0.2-1.4-0.4-2.2c-0.2-0.7-0.3-1.5-0.5-2.2c-0.7-3.1-1.7-6.3-2.9-9.7
			c-1.3-3.3-2.7-6.9-4.6-10.3c-1.9-3.4-4.3-6.8-7-9.8c-2.8-3.1-6.1-5.7-9.5-8.1c-3.4-2.4-7-4.5-10.6-6.6c-3.6-2.1-7.2-3.9-10.7-5.7
			c-3.5-1.8-7.1-3.3-10.4-4.7c-3.4-1.3-6.7-2.3-9.8-3.2c-3.1-0.9-5.9-1.6-8.5-2.2c-2.5-0.6-4.8-1-6.6-1.4C5.4,2.8,3.9,2.6,2.9,2.4
			C2,2.3,1.4,2.2,1.4,2.2l0,0C1.1,2.1,0.9,1.8,1,1.5C1,1.2,1.3,1,1.6,1.1c0,0,0.5,0.1,1.5,0.2c1,0.1,2.5,0.2,4.3,0.5
			c0.9,0.1,2,0.3,3.1,0.4c1.1,0.2,2.3,0.4,3.6,0.6c2.6,0.4,5.5,1,8.7,1.7C26,5.1,29.4,6,33,6.9C36.6,8,40.4,9,44.3,10.5
			C48.1,12,52,13.7,55.7,16c3.7,2.2,7.3,4.8,10.6,7.8c0.8,0.7,1.6,1.5,2.4,2.3c0.8,0.8,1.6,1.5,2.3,2.3c1.5,1.6,3,3.2,4.4,4.8
			c2.8,3.3,5.3,6.7,7.6,10.1c1.1,1.8,2.1,3.5,3,5.3c1,1.7,1.7,3.6,2.4,5.3c1.3,3.6,2.2,7,2.8,10.3c0.1,0.8,0.3,1.6,0.4,2.4
			c0.1,0.8,0.2,1.5,0.3,2.3c0.2,1.5,0.3,2.9,0.3,4.2c0.1,2.7,0,5-0.1,6.9c-0.1,1.9-0.3,3.4-0.4,4.4c-0.1,1-0.2,1.5-0.2,1.5
			c0,0.3-0.3,0.5-0.6,0.5C90.5,86.3,90.3,86.1,90.3,85.8z"/>
		<path d="M78.5,77.4c0.8,0.5,1.5,1.1,2.2,1.7c0.4,0.3,0.7,0.6,1.1,1c0.3,0.3,0.7,0.7,1,1c0.6,0.7,1.3,1.4,1.8,2.2
			c0.6,0.8,1.1,1.5,1.6,2.4c1,1.6,1.8,3.3,2.6,5l1.2,2.5c0.2,0.4,0.4,0.8,0.6,1.2c0.2,0.4,0.5,0.8,0.7,1.2l-3,0
			c0.3-0.7,0.7-1.4,1.1-2.1c0.4-0.7,0.9-1.3,1.3-2l2.3-4.3c0.8-1.4,1.5-2.9,2.3-4.3c0.8-1.4,1.7-2.8,2.7-4.1
			c0.2-0.2,0.4-0.2,0.6-0.1c0.2,0.1,0.2,0.4,0.1,0.6l0,0c-0.9,1.3-1.6,2.7-2.2,4.2c-0.6,1.5-1.2,2.9-1.8,4.4l-1.7,4.5
			c-0.3,0.7-0.5,1.6-0.7,2.3c-0.2,0.8-0.6,1.5-1,2.2l-1.8,3l-1.2-3c-0.4-0.9-0.7-1.7-1.1-2.6c-0.4-0.8-0.8-1.7-1.2-2.5l-1.2-2.4
			c-0.4-0.8-0.8-1.6-1.3-2.4c-0.8-1.6-1.7-3.1-2.7-4.6c-0.2-0.4-0.5-0.8-0.7-1.1c-0.3-0.4-0.5-0.7-0.8-1.1c-0.3-0.3-0.5-0.7-0.8-1.1
			c-0.3-0.4-0.6-0.7-0.9-1.1c-0.2-0.3-0.1-0.6,0.1-0.8C78.1,77.2,78.3,77.2,78.5,77.4z"/>
	</svg>
</div>

<div class="alert alert-come-back">
	<strong>Come back again for new effects!</strong>
	<button class="dismiss">OK</button>
</div>

<script>
'use strict';


const util = {
	math: {
		degToRad: alpha => alpha * Math.PI / 180,

		polarToDecart: (alpha, r) => {
			alpha = this.degToRad(alpha);
			return {x: r * Math.cos(alpha), y: r * Math.sin(alpha)};
		}
	},

	color: {
		random: (opts = {}) => {
			let h, s, l;

			h = opts.hue || Math.floor(Math.random() * 360);
			s = opts.saturation || Math.floor(Math.random() * 101);
			l = opts.lightness || Math.floor(Math.random() * 101);

			return `hsl(${h}, ${s}%, ${l}%)`;
		}
	}
};




/**
 * Make a contenteditable element more controllable
 */
class FunkyLetters {
	/**
	 * Constructor
	 * @param  {Element} el  document element with contenteditable=true or selector
	 * @param  {Object} [opts] options
	 */
	constructor(el, opts = {}) {
		if(typeof el == 'string') {
			el = document.querySelector(el);
		}
		this.container = el;
		this.options = opts;
		this._splitLetters();
		this._listenToInput();
	}


	/**
	 * Split container's text into one letter spans optionally colored
	 */
	_splitLetters() {
		this.container.innerHTML = FunkyLetters.splitTextLetters(this.container.textContent, this.options);
	}


	/**
	 * Split the text into one letter spans
	 * @param  {string} text input text
	 * @param  {Object} [opts] options
	 * @return {string}      html with text split into letters
	 */
	static splitTextLetters(text, opts = {}) {
		let letters;

		text = text.replace(/\s+/, ' ');
		letters = text.split(/(?=.)/);

		return letters.reduce((a, b) => a + FunkyLetters.makeLetterHtml(b, opts), '');
	}


	/**
	 * Generate html string for a letter
	 * @param  {string} letter single letter
	 * @param  {Object} [opts]   options
	 * @return {string}        html string
	 */
	static makeLetterHtml(letter, opts = {}) {
		let style = '',
			className = 'char';

		if(/\s/.test(letter)) {
			letter = ' ';
			className += ' space';
		} else {
			className += ' letter';
		}
		if(opts.colorize) {
			style += 'color:' + util.color.random({saturation: 100, lightness: 50}) + ';';
		}

		return `<span class="${className}" ${style && 'style="' + style + '"'}><span class="letter-inner">${letter}</span></span>`;
	}


	/**
	 * Watch input
	 */
	_listenToInput() {
		let me = this;
		// this.container.dataset.text = this.container.textContent;

		this.container.addEventListener('keydown', function(e) {
			let letterEl = me.getFocusLetter();

			if(e.key.length === 1 && !e.altKey && !e.ctrlKey) {
				e.preventDefault();
				me.insertText(e.key);
				return;
			}

			// If the container is empty
			if(!letterEl) return;

			switch(e.key) {
				// Firefox focuses in two steps on inline-block elements
				case 'ArrowRight':
					if(navigator.userAgent.indexOf('AppleWebKit') !== -1) break;
					if(!letterEl.nextElementSibling) break;
					e.preventDefault();
					me.setFocus(letterEl.nextElementSibling, 1);
					break;

				case 'ArrowLeft':
					if(navigator.userAgent.indexOf('AppleWebKit') !== -1) break;
					e.preventDefault();
					if(!letterEl.previousElementSibling) {
						me.setFocus(letterEl, 0);
					} else {
						me.setFocus(letterEl.previousElementSibling, 1);
					}
					break;

				case 'ArrowUp':
				case 'ArrowDown':
					e.preventDefault();
					break;

				case 'Home':
				case 'PageUp':
					e.preventDefault();
					me.setFocus(this.firstElementChild, 0);
					break;

				case 'End':
				case 'PageDown':
					e.preventDefault();
					me.setFocus(this.lastElementChild, 1);
					break;
			}
		});

		this.container.addEventListener('input', function(e) {
			// Firefox leaves empty containers when text is deleted. Make sure those are deleted too.
			me._cleanEmpty();
		});

		this.container.addEventListener('paste', function(e) {
			if(e.clipboardData.types.indexOf('text/plain') != -1) {
				e.preventDefault();
				me.insertText(e.clipboardData.getData('text/plain'));
			}
		});
	}

	/**
	 * Format text and insert it into the container at the caret position
	 * @param  {string} text the text to insert
	 */
	insertText(text) {
		let sel = document.getSelection(),
			range = document.createRange(),
			node = this.getFocusLetter(),
			isBeforeNode = sel.focusOffset === 0;

		sel.deleteFromDocument();
		if(!node) {
			this.container.insertAdjacentHTML('afterbegin', FunkyLetters.splitTextLetters(text, this.options));
			this.setFocus(Array.from(this.container.querySelectorAll('.char')).pop(), 1);
		} else if(isBeforeNode) {
			node.insertAdjacentHTML('beforebegin', FunkyLetters.splitTextLetters(text, this.options));
			this.setFocus(node.previousElementSibling, 1);
		} else {
			node.insertAdjacentHTML('afterend', FunkyLetters.splitTextLetters(text, this.options));
			for(let i = text.length; i > 0 && node.nextElementSibling; i--) {
				node = node.nextElementSibling;
			}
			this.setFocus(node, node.textContent.length);
		}
		
		this.container.dataset.changed = true;
		this._cleanEmpty();
	}

	/**
	 * Get the character in focus (at caret position)
	 * @return {Element} the element node in focus
	 */
	getFocusLetter() {
		const sel = document.getSelection();
		return sel.anchorNode.closest ? sel.anchorNode.closest('.char') : sel.anchorNode.parentElement.closest('.char');
	}

	/**
	 * Set cursor position
	 * @param {Element} node   letter element to focus on
	 * @param {Integer} offset offset. In our case, either 0 or 1
	 */
	setFocus(node, offset) {
		const sel = document.getSelection(),
			range = document.createRange();

		range.setStart(node, offset);
		sel.removeAllRanges();
		sel.addRange(range);
	}

	/**
	 * Delete elements other than .char the browser could have generated
	 */
	_cleanEmpty() {
		const focusLetter = this.getFocusLetter();
		Array.from(this.container.children).forEach(el => {
			if(el.classList.contains('char') && el.textContent) return;
			if(el === focusLetter) {
				if(el.previousElementSibling) {
					this.setFocus(el, 1);
				} else if (el.nextElementSibling) {
					this.setFocus(el.nextElementSibling, 1);
				}
			}
			el.remove();
		});
	}
}





/**
 * Control animations of an element's children
 */
class Animator {
	/**
	 * Constructor
	 * @param  {Element|string} el the container element whose children are being animated
	 */
	constructor(el) {
		this.container = el;
		this._removeClassTimer = null;

		// this.container.addEventListener('animationend', () => {
		// 	clearTimeout(this._removeClassTimer);
		// 	this._removeClassTimer = setTimeout(() => {
		// 		this.container.classList.remove('anim');
		// 	}, 900);
		// });
	}


	/**
	 * Run animation using the effect
	 * @param  {string} effect effect name
	 */
	animate(effect) {
		const cont = this.container;
		if(cont.classList.contains('anim')) {
			cont.classList.remove('anim');
			setTimeout(() => {
				this.animate(effect);
			}, 50);
			return;
		}
		clearTimeout(this._removeClassTimer);
		cont.classList.add('anim');
		if(cont.dataset.effect === effect && !('changed' in cont.dataset)) return;
		cont.dataset.effect = effect;
		delete cont.dataset.changed;
		// if(effect !== 'converge'/* && effect !== 'spiral'*/ && effect !== 'meteorite') {
		// 	Array.prototype.forEach.call(cont.children, function(el) {
		// 		el.style.transform = '';
		// 	});
		// }
		if(!Animator.effects[effect]) {
			throw new Error(`Animator: effect ${effect} is not defined`);
		}
		if(Animator.effects[effect].delays) {
			this.distributeDelays(Animator.effects[effect].delays);
		} else {
			this.distributeDelays({shift: false});
		}
	}


	/**
	 * Distribute animation delays
	 * @param  {Object} opts           options
	 * @param  {Object} opts.shift     shift each next item this much milliseconds
	 * @param  {Object} [opts.random]  distribute delays randomly: without regard to document order
	 * @param  {Object} [opts.reverse] distribute delays in reverse document order starting with the last element
	 */
	distributeDelays(opts) {
		let shift = opts.shift != null ? opts.shift : 100,
			curShift = 0,
			els = Array.from(this.container.children);

		if(opts.random) {
			let newEls = [];
			for(let j = 0, l = els.length; j < l; j++) {
				let i = Math.floor(Math.random() * els.length);
				newEls.push(els.splice(i, 1)[0]);
			}
			els = newEls;
		}
		if(opts.reverse) {
			els = els.reverse();
		}

		els.forEach(el => {
			curShift += typeof shift == 'object' ? Math.round(Math.random() * (shift.max - shift.min) + shift.min) : shift;
			el.style.animationDelay = el.querySelector('.letter-inner').style.animationDelay = '';
			if(shift === false) return;
			el.style.animationDelay = (parseFloat(getComputedStyle(el, null).animationDelay) + curShift/1000) + 's';
			el.querySelector('.letter-inner').style.animationDelay = (parseFloat(getComputedStyle(el.querySelector('.letter-inner'), null).animationDelay) + curShift/1000) + 's';
		});
	}


	/**
	 * Distribute children's offset positions
	 * We are currently doing this in Sass
	 * @param  {Object} opts options
	 */
	distributeOffsets(opts) {
		let coords,
			alpha = opts.minAngle || 0,
			x = 100,
			y = 100,
			els = this.container.children;

		for(let i = 0; i < els.length; i++) {
			if(opts.dx || opts.dy) {
				x -= opts.dx || 0;
				y -= opts.dy || 0;
			} else {
				if(opts.random) {
					alpha = Math.random * (opts.maxAngle || 360 - opts.minAngle || 0) + opts.minAngle || 0;
					coords = util.math.polarToDecart(alpha, 100);
				} else {
					coords = util.math.polarToDecart(alpha, 100);
					alpha += opts.dAlpha;
				}
				x = coords.x;
				y = coords.y;
			}
			els[i].style.transform = 'translate(' + x.toFixed(3) + 'vmax,' + y.toFixed(3) +'vmax)';
		}
	}
}
/**
 * The available animation effects and their settings
 * @type {Object}
 */
Animator.effects = {
	roll: {
		delays: {shift: 100}
	},
	slide: {
		delays: {shift: 100}
	},
	swivel: {
		delays: {shift: 100, random: true}
	},
	peel: {
		delays: {shift: 70}
	},
	wave: {
		delays: {shift: 30}
	},
	wave2: {
		delays: {shift: 120}
	},
	hop: {
		delays: {shift: 140}
	},
	converge: {
		delays: {shift: false}
	},
	fade: {
		delays: {shift: 80, random: true}
	},
	snow: {
		delays: {shift: 600, random: true}
	},
	spiral: {
		delays: {shift: 100}
	},
	meteorite: {
		delays: {shift: 50, random: true}
	},
	bounce: {
		delays: {shift: 200, random: true}
	},
	float: {
		delays: {shift: 400, random: true}
	},
	bubble: {
		delays: {shift: {min: 200, max: 500}, random: true}
	},
};





const animationContainer = document.querySelector('.anim-text');

let config = localStorage['funkyLetters:config'];
try {
	config = JSON.parse(config);
} catch(e) {
	config = {
		completed: {}
	};
}

// Tips
if(config.completed.changeEffect) {
	document.querySelector('.tip-effect').classList.add('hide');
} else {
	document.querySelector('#selectEffect').addEventListener('change', () => {
		document.querySelector('.tip-effect').classList.add('hide');
		config.completed.changeEffect = true;
		localStorage['funkyLetters:config'] = JSON.stringify(config);
	}, {once: true});
}

if(config.completed.type) {
	document.querySelector('.tip-type').classList.add('hide');
} else {
	animationContainer.addEventListener('keydown', () => {
		document.querySelector('.tip-type').classList.add('hide');
		config.completed.type = true;
		localStorage['funkyLetters:config'] = JSON.stringify(config);
	}, {once: true});
}

if(config.completed.comeBack) {
	document.querySelector('.alert-come-back').classList.add('hide');
}


new FunkyLetters(animationContainer, {colorize: true});

const animator = new Animator(animationContainer);
animator.animate(document.querySelector('#selectEffect').value);


// Listen to controls
document.querySelector('#selectEffect').addEventListener('change', function(e) {
	animator.animate(this.value);
});
document.querySelector('.animate').addEventListener('click', function(e) {
	animator.animate(document.querySelector('#selectEffect').value);
});


// Animate on enter key
animationContainer.addEventListener('keydown', function(e) {
	switch(e.keyCode) {
		case 13:
			e.preventDefault();
			document.querySelector('.animate').focus();
			document.querySelector('.animate').click();
			break;
	}
});




// Other
document.querySelector('.dismiss').addEventListener('click', function(e) {
	this.closest('.alert').classList.add('close');
	config.completed.comeBack = true;
	localStorage['funkyLetters:config'] = JSON.stringify(config);
});
</script>
</body>
</html>
